﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Globalization;
using Tomahawk.Serialization;
using Tomahawk.Serialization.SerialBoost;

namespace Tomahawk.Runtime.Localization
{
    /// <summary>
    /// Delegate to notify language changes
    /// </summary>
    public delegate void LanguageChangedDelegate(LocalizationManager.Language newLanguage);

    /// <summary>
    /// This class manages localization of strings
    /// </summary>
    public class LocalizationManager
    {

        /// <summary>
        /// Event raised when the language has changed
        /// </summary>
        public event LanguageChangedDelegate LanguageChanged;

        /// <summary>
        /// Available languages
        /// </summary>
        public enum Language
        {
            En = 0,
            Es,
        }

        /// <summary>
        /// A single localized phrase
        /// </summary>
        public class Phrase
        {
            [XmlAttribute("id")]
            public string Id;

            [XmlText()]
            public string Text;
        }

        /// <summary>
        /// A package is a group of localized phrases that can be loaded and unloaded at runtime
        /// </summary>
        public class LocalizationPackage
        {
            [XmlAttribute("name")]
            public string PackageName;

            [XmlAttribute("language")]
            public Language Language;

            [XmlElement("Phrases")]
            public SerializableList<Phrase> Phrases;

            [XmlIgnore()]
            public Dictionary<String, String> PhraseDictionary;

            [XmlIgnore()]
            public string Path;

            /// <summary>
            /// Constructor
            /// </summary>
            public LocalizationPackage()
            {
                Phrases = new SerializableList<Phrase>();
                PhraseDictionary = new Dictionary<string, string>();
            }

            /// <summary>
            /// Load all phrases into dictionary
            /// </summary>
            public void LoadDictionary()
            {
                PhraseDictionary.Clear();
                foreach (Phrase ph in this.Phrases)
                {
                    PhraseDictionary.Add(ph.Id, ph.Text);
                }
                // clear the original container to save some mem
                Phrases.Clear();
            }

        }

        /// <summary>
        /// Dictionary containing all the loaded packages
        /// </summary>
        private Dictionary<String, LocalizationPackage> loadedPackages;

        /// <summary>
        /// Language currently set
        /// </summary>
        private Language currentLanguage;

        /// <summary>
        /// Constructor
        /// </summary>
        public LocalizationManager()
        {
            loadedPackages = new Dictionary<string, LocalizationPackage>();

            //the default language to use will be got from the system culture
            this.currentLanguage = this.GetSystemLanguage();
        }

        /// <summary>
        /// Get the language of the system
        /// </summary>
        /// <returns></returns>
        public Language GetSystemLanguage()
        {
            Language retVal = Language.En;

            //language selection
            switch (CultureInfo.CurrentCulture.TwoLetterISOLanguageName)
            {
                case "es":
                    retVal = LocalizationManager.Language.Es;
                    break;
                default:
                    retVal = LocalizationManager.Language.En;
                    break;
            }

            return retVal;
        }

        /// <summary>
        /// Change the current language
        /// </summary>
        public bool SetLangugage(Language newLanguage)
        {
            bool changed = false;

            //reload packages if necesary
            if (this.currentLanguage != newLanguage)
            {
                //change the current language
                this.currentLanguage = newLanguage;

                //Reload packages
                this.reloadPackages();

                //raise event
                if (this.LanguageChanged != null) this.LanguageChanged(newLanguage);

                changed = true;
            }
            else
            {
                this.currentLanguage = newLanguage;
            }
            return changed;
        }

        /// <summary>
        /// Reload all packages (for language change, for instance)
        /// </summary>
        private void reloadPackages()
        {
            //get a copy of the loaded packages
            LocalizationPackage[] reloadPackages = this.loadedPackages.Values.ToArray<LocalizationPackage>();

            //clear the packages
            this.loadedPackages.Clear();

            //reload each package
            foreach (LocalizationPackage pack in reloadPackages)
            {
                this.LoadPackage(pack.Path);
            }
        }

        /// <summary>
        /// Load a package from file 
        /// </summary>
        /// <param name="packagePath">Path to the lozalizatin file, extension will be added upon the current language</param>
        public string LoadPackage(string packagePath)
        {
            //compose the filename
            string extension = "." + this.currentLanguage.ToString();
            string fileName = packagePath + extension;

            LocalizationPackage package = null;

            //load object using serialization
            if (System.IO.File.Exists(fileName + "c") && Engine.Instance.EngineConfiguration.SerialBoostEnabled)
            {
                package = (LocalizationPackage)SerialBoost.Instance.Deserialize(typeof(LocalizationPackage), fileName + "c");
            }
            else
            {
                SerialBoost.ForceAllUseXmlSerializer = true;
                package = (LocalizationPackage)SerialBoost.Instance.Deserialize(typeof(LocalizationPackage), fileName);
                SerialBoost.ForceAllUseXmlSerializer = false;
            }

            package.LoadDictionary();
            package.Path = packagePath;     //save for later use

            //add to dictionary (replace if a package with same id already exists)
            LocalizationPackage existing = null;
            if (this.loadedPackages.TryGetValue(package.PackageName, out existing))
            {
                this.loadedPackages.Remove(existing.PackageName);
            }
            this.loadedPackages.Add(package.PackageName, package);

            //return package name
            return package.PackageName;
        }

        /// <summary>
        /// Get a localized string.
        /// Returns false if not found.
        /// </summary>
        public bool GetLocalizedString(string Id, out string Localized)
        {
            bool retVal = false;
            string locString = "";

            if (Id.StartsWith("##"))
            {
                int hatPos = Id.IndexOf('@');
                if (hatPos > -1)
                {
                    //format ##id@package
                    string phrase = Id.Substring(2, hatPos - 2);
                    string package = Id.Substring(3 + phrase.Length);

                    LocalizationPackage locPackage = null;
                    if (this.loadedPackages.TryGetValue(package, out locPackage))
                    {
                        if (locPackage.PhraseDictionary.TryGetValue(phrase, out locString))
                        {
                            retVal = true;
                        }
                    }
                }
                else
                {
                    //format ##id
                    string phrase = Id.Substring(2);

                    foreach (LocalizationPackage locPackage in this.loadedPackages.Values)
                    {
                        if (locPackage.PhraseDictionary.TryGetValue(phrase, out locString))
                        {
                            retVal = true;
                            break;
                        }
                    }
                }

                //If the localization was not found, add a (!LOC) at the beggining
                if (retVal)
                {
                    Localized = locString;
                }
                else
                {
                    Localized = "(!LOC_FND) " + Id;
                }
            }
            else
            {
                Localized = "(!LOC) " + Id;
            }

            return retVal;
        }

        /// <summary>
        /// Remove a package from the manager
        /// </summary>
        public void FreePackage(string packageName)
        {
            LocalizationPackage pack = null;
            if (this.loadedPackages.TryGetValue(packageName, out pack))
            {
                this.loadedPackages.Remove(packageName);
            }
        }

    }

}
